package com.javadaemon.rpgengine.screens;

import java.util.ArrayList;
import java.util.Random;

import org.newdawn.slick.Animation;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.SpriteSheet;

import com.javadaemon.rpgengine.GameMain;
import com.javadaemon.rpgengine.model.Actor;
import com.javadaemon.rpgengine.model.DIRECTION;
import com.javadaemon.rpgengine.model.GameState;
import com.javadaemon.rpgengine.model.TILE_OBJECT;
import com.javadaemon.rpgengine.model.TILE_TYPE;
import com.javadaemon.rpgengine.model.Tile;
import com.javadaemon.rpgengine.model.TileMap;
import com.javadaemon.rpgengine.model.TileObject;
import com.javadaemon.rpgengine.model.Actor.ACTOR_STATE;
import com.javadaemon.rpgengine.util.ResourceManager;


/*
 Copyright (c) 2011, Morten Andersen
 All rights reserved.

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright
 notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 notice, this list of conditions and the following disclaimer in the
 documentation and/or other materials provided with the distribution.
 * The name of the author may not be used to endorse or promote products
 derived from this software without specific prior written permission.

 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED. IN NO EVENT SHALL MORTEN ANDERSEN BE LIABLE FOR ANY
 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * The UI for the main map screen, where movement in the world is performed. This UI screen draws the tiles of the current map, and all actors and contents of
 * these tiles, and allows the player to move around in the world.
 * 
 * @author Somnium
 * 
 */
public class MapScreen implements Screen {

    // Constants used to handle screen offset and view port cropping.
    private static final int fitsInViewX = GameMain.SCREEN_WIDTH
	    / ResourceManager.TILE_WIDTH;
    private static final int fitsInViewY = GameMain.SCREEN_HEIGHT
	    / ResourceManager.TILE_HEIGHT + 1;
    private static final int CENTER_OFFSET_X = (GameMain.SCREEN_WIDTH / 2)
	    - (ResourceManager.TILE_WIDTH / 2);
    private static final int CENTER_OFFSET_Y = (GameMain.SCREEN_HEIGHT / 2)
	    - (ResourceManager.TILE_HEIGHT / 2);

    private GameState model; // A reference to the game state of the current game
    private ArrayList<Actor> characters; // Used when drawing moving characters.

    public MapScreen(GameState model) {
	this.model = model;
	characters = new ArrayList<Actor>();
    }

    public void render(GameContainer container, Graphics g)
	    throws SlickException {

	// TODO: Make these variables controllable, from higher up.
	g.scale(2,2);
	g.translate(-800+1200/2, -600+900/2);

	// We need the actor early on, because we draw everything in relation to
	// it

	Actor player = model.getPlayer();
	int px = player.getWorldX();
	int py = player.getWorldY();

	// Draw the current tile map, and the actors in it

	final TileMap map = model.getMap(player.getMapId()); // Get the
							     // currently
							     // active
							     // (displayed)
							     // map from the
							     // game state
	final SpriteSheet graphics = ResourceManager.getRessourceManager()
		.getGraphics(); // Get the sprite sheet

	// Sets the offset of the viewport, depending on the players location
	// and the fact that
	// the player is always the center of the viewport.
	int viewportOffsetX = CENTER_OFFSET_X - (px);
	int viewportOffsetY = CENTER_OFFSET_Y - (py);

	// Check boundaries for viewport (for cropping purposes - we do not want
	// to loop through and draw tiles
	// which are outside the currently visible part of the map)
	int firstVisibleX = ((int) -viewportOffsetX)
		/ ResourceManager.TILE_WIDTH;
	int lastVisibleX = Math.min(firstVisibleX + fitsInViewX + 1,
		map.getWidth());

	int firstVisibleY = ((int) -viewportOffsetY)
		/ ResourceManager.TILE_HEIGHT;
	int lastVisibleY = Math.min(firstVisibleY + fitsInViewY + 1,
		map.getHeight());

	graphics.startUse(); // Until released by endUse, only graphics from
			     // this sprite sheet will be drawn, but they
			     // will be drawn faster.

	// Draw all visible tiles (with contents, if any)
	for (int x = firstVisibleX; x < lastVisibleX; x++) {
	    int currentXOffset = x * ResourceManager.TILE_WIDTH;
	    for (int y = firstVisibleY; y < lastVisibleY; y++) {
		int currentYOffset = y * ResourceManager.TILE_HEIGHT;
		if (x >= 0 && y >= 0) {
		    // Rendering the first layer - raw tiles
		    Tile tile = map.getTile(x, y);
		    if (tile != null) {
			// Using the image reference key from the tile's type,
			// retrieve and draw the terrain image
			TILE_TYPE type = tile.getType();
			ResourceManager.IMAGE image = type.getImage();
			Image imageToDraw = graphics.getSubImage(
				image.getSheetColumn(), image.getSheetRow());
			imageToDraw.drawEmbedded(viewportOffsetX
				+ currentXOffset, viewportOffsetY
				+ currentYOffset, ResourceManager.TILE_WIDTH,
				ResourceManager.TILE_HEIGHT); // Note that the drawEmbedded method is used to make use of the enhanced drawing speed.
			
			// Draw possible object on this tile
			TileObject tileObject = tile.getObject();
			if (tileObject != null) {
			    TILE_OBJECT object = tileObject.getType();
			    if (object != null) {
				ResourceManager.getRessourceManager()
					.getObjectRender(object.getRender()).render(container, g, graphics, 
						viewportOffsetX + currentXOffset,
						viewportOffsetY + currentYOffset);
			    }
			}
			
			// Draw possible Actor on this tile
			 Actor actor = tile.getActor(); 
			 if (actor != null && actor.getCurrentState() == ACTOR_STATE.IDLE) { 
			     Animation currentAnimation = actor .getCurrentAnimation();
			     currentAnimation.getCurrentFrame().drawEmbedded( viewportOffsetX + actor.getWorldX(), viewportOffsetY + actor.getWorldY() - 4,
					 ResourceManager.TILE_WIDTH, ResourceManager.TILE_HEIGHT);
			 } else if (actor != null) {
			     characters.add(actor); // Add to the list, so we'll render this Actor later.
			 }
		    }
		}
	    }
	}
	
	// Draw the moving characters
	for (Actor actor : characters) {
	    if (actor != null) {
		Animation currentAnimation = actor.getCurrentAnimation();
		currentAnimation.getCurrentFrame().drawEmbedded(
			viewportOffsetX + player.getWorldX(),
			viewportOffsetY + player.getWorldY() - 4,
			ResourceManager.TILE_WIDTH,
			ResourceManager.TILE_HEIGHT);
	    }
	}
	if (!characters.isEmpty()) {
	    characters.clear();
	}

	graphics.endUse(); // Release, so that graphics that are not in the sprite sheet can be used again.
    }

    public void update(GameContainer container, int delta)
	    throws SlickException {
	Input input = container.getInput();
	Actor player = model.getPlayer();
	TileMap map = model.getMap(player.getMapId());

	// Exit game if ESCAPE is pressed
	if (input.isKeyPressed(Input.KEY_ESCAPE)) {
	    container.exit();
	}

	// Detect movement
	DIRECTION activeDirection = null;
	if (input.isKeyDown(Input.KEY_NUMPAD8) || input.isKeyDown(Input.KEY_UP)) {
	    activeDirection = DIRECTION.NORTH;
	} else if (input.isKeyDown(Input.KEY_NUMPAD2)
		|| input.isKeyDown(Input.KEY_DOWN)) {
	    activeDirection = DIRECTION.SOUTH;
	} else if (input.isKeyDown(Input.KEY_NUMPAD4)
		|| input.isKeyDown(Input.KEY_LEFT)) {
	    activeDirection = DIRECTION.WEST;
	} else if (input.isKeyDown(Input.KEY_NUMPAD6)
		|| input.isKeyDown(Input.KEY_RIGHT)) {
	    activeDirection = DIRECTION.EAST;
	}

	// Interaction
	if (input.isKeyPressed(Input.KEY_X)) {
	    // Get the location of the object you're trying to interact with
	    int tileX = player.getTileX() + player.getCurrentDirection().DX();
	    int tileY = player.getTileY() + player.getCurrentDirection().DY();
	    
	    Actor actor = map.getTile(tileX, tileY).getActor();
	    if (actor != null) {
		actor.setFacingDirection(player.getCurrentDirection().getOpposite());
	    }
	}

	// Update player object
	player.update(container, delta, map);

	// React to movement command - only react if the player actor is idle
	if (Actor.ACTOR_STATE.IDLE.equals(player.getCurrentState())
		&& activeDirection != null) {
	    // Start movement
	    boolean success = performMove(player, activeDirection);
	    if (!success) {
		// Movement was blocked by terrain - maybe put a "blocked" sound
		// here.
	    }
	}
    }

    /**
     * Helper method which determines whether a move is legal, and if so will instruct the actor object to perform the move.
     * 
     * @param actor
     *            The actor to move
     * @param direction
     *            The direction that the actor should move in
     * @return True if the movement is valid, false otherwise
     * @throws SlickException
     */
    private boolean performMove(Actor actor, DIRECTION direction)
	    throws SlickException {
	boolean returnValue = false;

	Actor player = model.getPlayer();
	TileMap map = model.getMap(player.getMapId());

	// Calculate the destination coordinates, and check if it is legal for
	// the actor to move to that tile.
	int tileDestX = actor.getTileX() + direction.DX();
	int tileDestY = actor.getTileY() + direction.DY();
	Tile destTile = model.getMap(actor.getMapId()).getTile(tileDestX,
		tileDestY);

	// Check if there is anything in the way
	returnValue = (destTile != null) && (!destTile.isBlocking());

	// If movement is valid, instruct the actor to begin the movement
	if (returnValue) {
	    actor.initiateMove(tileDestX, tileDestY, map, direction);
	} else {
	    actor.setFacingDirection(direction);
	}

	return returnValue;
    }

}
